package com.googlecode.scenemaker3d.view
{	
	import com.googlecode.scenemaker3d.controller.ICameraController;
	import com.googlecode.scenemaker3d.controller.IObjectController;
	
	import com.googlecode.scenemaker3d.events.Position3DEvent;
	
	import flash.events.ContextMenuEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	
	import com.googlecode.scenemaker3d.model.VO_Object3D;
	import com.googlecode.scenemaker3d.model.World3DModel;
	
	import mx.collections.ArrayCollection;
	import mx.controls.Button;
	import mx.controls.ComboBox;
	import mx.controls.HSlider;
	import mx.core.Application;
	import mx.core.UIComponent;
	import mx.events.CollectionEvent;
	import mx.events.ListEvent;
	import mx.events.PropertyChangeEvent;
	import mx.events.SliderEvent;
	
	import org.papervision3d.cameras.Camera3D;
	import org.papervision3d.core.geom.renderables.Vertex3D;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.core.utils.InteractiveSceneManager;
	import org.papervision3d.events.InteractiveScene3DEvent;
	import org.papervision3d.materials.special.LineMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.view.layer.ViewportLayer;
	
	import com.googlecode.scenemaker3d.services.CameraViewType;
	import com.googlecode.scenemaker3d.services.Control;
	import com.googlecode.scenemaker3d.services.Initialize;
	import com.googlecode.scenemaker3d.services.RenderScene;
	import com.googlecode.scenemaker3d.services.ScreenTo3D;
	import com.googlecode.scenemaker3d.services.World;
	
	public class Viewport extends UIComponent
	{
		
		private var wModel:World3DModel;
		private var cCamera:ICameraController;
		private var cObject:IObjectController;
		
		private var cb:ComboBox;
		private var dp:ArrayCollection;
		
		private var zoomControl:HSlider;
		
		private var viewToggle:Button = new Button();
		//private	var zx:Button = new Button();
		
		private var viewport:Viewport3D;
		private var vpl:ViewportLayer;
		private var vWidth:Number = 800;
		private var vHeight:Number = 800;
		private var mouseInViewport:Boolean = false;
		
		private var cameraObject:DisplayObject3D;
		private	var cameraTarget:DisplayObject3D;
		private static var cameraLine:DrawLine;
		
		private var convertPos:ScreenTo3D;
		
		private var dragObj:DisplayObject3D;
		private var dragObjOffset:Number3D;
		private var dragging:Boolean = false;
		
		private var ISM:InteractiveSceneManager;

		public var setView:String;

		public function Viewport()
		{	
			wModel = Control.instance.world3DModel;
			wModel.objects.addEventListener(CollectionEvent.COLLECTION_CHANGE, onObjectEvent);
			
			cCamera = Control.instance.cameraController;
			cObject = Control.instance.objectController;
			
			viewport = new Viewport3D(vWidth, vHeight, false, false);
			convertPos = new ScreenTo3D(viewport);
			
			wModel.cameras.addEventListener(CollectionEvent.COLLECTION_CHANGE, onCameraChange);
			wModel.addEventListener(Position3DEvent.POSITION_3D, onObject3DMove);
			Application.application.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUp);
			Application.application.addEventListener(MouseEvent.CLICK, onStageMouseUp);
			Application.application.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			
			this.addEventListener(MouseEvent.ROLL_OVER, onViewportOver);
			this.addEventListener(MouseEvent.ROLL_OUT, onViewportOver);
			
			viewport.interactive = true;

			ISM = viewport.interactiveSceneManager;
			ISM.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, onEvent3D);

		}
		//FLEX HANDLERS
		override protected function commitProperties():void
		{
			super.commitProperties();
			
			if(setView != "")
			{
				for(var i:int=0; i<dp.length; i++)
				{
					if(setView == dp.getItemAt(i).label)
					{
						cb.selectedIndex = i;
						break;
					}					
				}
			}

			//Set up view toggle
			if(cb.selectedLabel == CameraViewType.ACTUAL_VIEW || cb.selectedLabel == CameraViewType.MAIN_VIEW)
			{
				this.addChild(viewToggle);
			}
			//Set up 3D plane
			set3DPlane();
			//Set grid visibility
			setGridVisibilities();
			//Set zoom
			cCamera.setCameraZoom(cb.selectedLabel, Initialize.DEFAULT_CAMERA_ZOOM);
			//Render
			RenderScene.instance.addEventListener("renderScene", onRender);
		}
		override protected function createChildren():void
		{
			super.createChildren();

			viewport.graphics.beginFill(0xff0000, 0);
			viewport.graphics.drawRect(0, 0, vWidth, vHeight);

			
			this.addChild(viewport);
			this.setChildIndex(viewport, 0);
			
			createCameraDropdown();
			createZoomControl();
			//createPlaneToggleControls();
						
			//Make camera target and camera object interactive  			
			var vpl:ViewportLayer;
			cameraObject = cObject.getObjectListByType("cameraObject")[0].data;
			cameraTarget = cObject.getObjectListByType("cameraTarget")[0].data;
			
			vpl = viewport.getChildLayer(cameraObject);
			vpl.addEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
			
			vpl = viewport.getChildLayer(cameraTarget);
			vpl.addEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
			
			createCameraLine();
					
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);

			//Adjust so viewport misses controls at top
			var topOffset:uint = 30;
			//Centre viewport on resize
			var offsetX:Number = (vWidth - this.parent.width) / 2;
			var offsetY:Number = (vHeight - this.parent.height) / 2;
			
			var viewArea:Rectangle = new Rectangle(offsetX, offsetY, this.parent.width, this.parent.height - topOffset);
			viewport.scrollRect = viewArea; 
			viewport.y = topOffset;
						
		}
		//EVENT HANDLERS
		private function onCameraChange(e:CollectionEvent):void
		{
			for(var i:int =0; i<e.items.length;i++)
			{
				if (e.items[i] is PropertyChangeEvent) 
				{
                    if (PropertyChangeEvent(e.items[i]) != null) 
                    {
                    	switch(e.items[i].property)
                    	{
                    		case "zoom":
                    			setZoomControl(e.items[i]);
                    			break;
                    	}
                    }
    			}
			}	
		}
		private function onCB(e:ListEvent):void
		{
			var activeCamera:Object = cCamera.getCameraByName(cb.selectedLabel);
			
			if(activeCamera.viewportZoom > 0){
				zoomControl.value = activeCamera.viewportZoom;
			}	
			//Set up 3D plane
			set3DPlane();
			//Set grid visibility
			setGridVisibilities();
			//If the view is Actual View, turn off the visibility of camera target
			if(cb.selectedLabel == CameraViewType.ACTUAL_VIEW)
			{
				setObjectVisible("cameraTarget", false);
				setObjectVisible("cameraLine", false);
			}else
			{
				setObjectVisible("cameraTarget", true);
				setObjectVisible("cameraLine", true);
			}
			
			if(cb.selectedLabel == CameraViewType.ACTUAL_VIEW || cb.selectedLabel == CameraViewType.MAIN_VIEW)
			{
				this.addChild(viewToggle);
			}else
			{
				if(viewToggle.parent == this)
				{
					this.removeChild(viewToggle);
				}
			}
			 
		}
		private function onDeleteObject(e:ContextMenuEvent):void
		{
			//Set the selected object
			cObject.setSelectedObject((e.contextMenuOwner as ViewportLayer).displayObject3D.name);
			//Delete selected object - via controller
			cObject.removeObject((e.contextMenuOwner as ViewportLayer).displayObject3D.name);
		}
		private function onEvent3D(e:InteractiveScene3DEvent):void{
			//
		}
		private function onKeyDown(e:KeyboardEvent):void
		{
			switch(e.keyCode)
			{
				case 88:
					if(mouseInViewport)
					{
					//Select Actual view of viewport over which mouse is hovering
						for(var i:int=0; i<dp.length; i++)
						{
							if(CameraViewType.ACTUAL_VIEW == dp.getItemAt(i).label)
							{
								cb.selectedIndex = i;
								break;
							}					
						}
						setObjectVisible("cameraTarget", false);
						setObjectVisible("cameraLine", false);			
					}
					break;
				case 67:
					if(mouseInViewport)
					{
					//Select Actual view of viewport over which maouse is hovering
						for(i=0; i<dp.length; i++)
						{
							if(CameraViewType.MAIN_VIEW == dp.getItemAt(i).label)
							{
								cb.selectedIndex = i;
								break;
							}					
						}
						setObjectVisible("cameraTarget", true);
						setObjectVisible("cameraLine", true);		
					}
					break;					
			}
			//Set up 3D plane
			set3DPlane();
			//Set grid visibility
			setGridVisibilities();
		}
		private function onObjectEvent(e:CollectionEvent):void
		{
			switch(e.kind)
			{
				case "add":
					objectAdded(e);
					break;
			}
		}
		private function onObjectSelected(e:MouseEvent):void
		{			
			//Store reference to the selected object
			dragObj = (e.currentTarget as ViewportLayer).displayObject3D;
			//Get reference to the VO
			var id:String = dragObj.name;
			//Update model of selected object, using controller
			cObject.setSelectedObject(id);
			
			
			//Get the vector from the click point to the object(origin).
			var dragObjOrigin:Number3D = new Number3D(dragObj.x, dragObj.y, dragObj.z);
			
			//Ensure the Plane3D bisects the object centre	
			set3DPlane(new Number3D(dragObj.x, dragObj.y, -dragObj.z));
			
			if(cb.selectedLabel == CameraViewType.TOP_VIEW)
			{
				set3DPlane(new Number3D(dragObj.x, -dragObj.y, dragObj.z));
			}
				
			var clickPt:Number3D = convertPos.get3DPoint(cb.selectedItem.data);
			
			dragObjOffset = Number3D.sub(dragObjOrigin, clickPt);
					
			dragging = true;		
		}
		private function onObject3DMove(e:Position3DEvent = null):void
		{
			cameraObject.lookAt(cameraTarget);
			//Make the Actual View camera follow the cameraObj
			//TEST
			var actualView:Camera3D = cCamera.getCameraListByType(CameraViewType.ACTUAL_VIEW)[0].data;
	
			actualView.copyPosition(cameraObject);
			actualView.lookAt(cameraTarget);
			
			//Update camera line
			var vt1:Vertex3D = new Vertex3D(cameraObject.x, cameraObject.y, cameraObject.z);
			var vt2:Vertex3D = new Vertex3D(cameraTarget.x, cameraTarget.y, cameraTarget.z);
	
			cameraLine.update(vt1, vt2);
		}
		private function onRender(e:Event):void
		{
			var scene:Scene3D = World.instance.scene;
			var camera:Camera3D = cb.selectedItem.data;
			
			if(dragging && dragObj!= null)
			{	
				var pt:Number3D = convertPos.get3DPoint(cb.selectedItem.data);
				dragObj.x = pt.x + dragObjOffset.x;
				dragObj.y = pt.y + dragObjOffset.y;
				dragObj.z = pt.z + dragObjOffset.z;  
			
				onObject3DMove();				
			}
			
			World.instance.renderer.renderScene(scene, camera, viewport);
		}
		private function onStageMouseUp(e:MouseEvent):void
		{
			if(dragObj)
			{
				//Save position to model (via controller)
				var pos:Number3D = new Number3D(dragObj.x, dragObj.y, dragObj.z);				
				cObject.setObjectPos(dragObj.name, pos);
				
				dragObj = null;
				dragging = false;
				set3DPlane();				
			}
			
		}
		private function onViewportOver(e:MouseEvent):void
		{
			if(e.type == "rollOver")
			{
				mouseInViewport = true;
			}else if(e.type == "rollOut")
			{
				mouseInViewport = false;
			}
		}
		private function onViewToggle(e:MouseEvent):void
		{
			if(viewToggle.label == "xy")
			{
				viewToggle.label = "zx";
			}else
			{
				viewToggle.label = "xy";
			}
			set3DPlane();
		}
		private function onZoom(e:SliderEvent):void
		{
			//Delegate to controller		
			cCamera.setCameraZoom(cb.selectedLabel, Math.round(e.value));
		}
		//PRIVATE FUNCTIONS		
		private function createCameraDropdown():void
		{
			dp = wModel.cameras;
			
			//Create view selector. Add viewport.
			if(!cb)
			{
				cb = new ComboBox();
				with(cb)
				{
					width = 110;
					height = 17;
					dataProvider = dp;
					addEventListener(ListEvent.CHANGE, onCB);
				}
				
				this.addChild(cb);	
			}
		}
		private function createZoomControl():void
		{
			//Add Zoom control.
			if(!zoomControl)
			{
				zoomControl = new HSlider();
				
				with(zoomControl)
				{
					minimum = 10;
					maximum = 110;
					tickInterval = 20;
					width = 50;
					x = cb.x + cb.width + 3;
					y = 10;
					setThumbValueAt(0, Initialize.DEFAULT_CAMERA_ZOOM);
					liveDragging = true;
					addEventListener(SliderEvent.CHANGE, onZoom);
				}			
				this.addChild(zoomControl);
			}
		}
		private function createPlaneToggleControls():void
		{	
			with(viewToggle)
			{
				width = 40;
				height = 17;
				label = "zx";
				addEventListener(MouseEvent.CLICK, onViewToggle);
			}
			
			viewToggle.x = zoomControl.x + zoomControl.width + 3;
		}
		
		private function createCameraLine():void
		{
			//Add camera line
			if(!cameraLine)
			{
				var lm:LineMaterial = new LineMaterial(0xff0000);
			
				var vt1:Vertex3D = new Vertex3D(cameraObject.x, cameraObject.y, cameraObject.z);
				var vt2:Vertex3D = new Vertex3D(cameraTarget.x, cameraTarget.y, cameraTarget.z);
			
				cameraLine = new DrawLine(lm, vt1, vt2);
				cameraLine.name = "cameraLine";
				cObject.addObject(cameraLine as DisplayObject3D);
			}
		}
		private function setGridVisibilities():void
		{
			var scene:Scene3D = World.instance.scene;
			
			var frontViewGrid:ViewportLayer = viewport.getChildLayer(scene.getChildByName(CameraViewType.FRONT_VIEW));
			var topViewGrid:ViewportLayer = viewport.getChildLayer(scene.getChildByName(CameraViewType.TOP_VIEW));
			var sideViewGrid:ViewportLayer = viewport.getChildLayer(scene.getChildByName(CameraViewType.LEFT_VIEW));
			
			frontViewGrid.mouseEnabled = false;
			topViewGrid.mouseEnabled = false;
			sideViewGrid.mouseEnabled = false;
			
			 switch(cb.selectedLabel)
			 {
				case CameraViewType.FRONT_VIEW:
					frontViewGrid.visible = true;
					topViewGrid.visible = false;
					sideViewGrid.visible = false;
					
					break;
				case CameraViewType.TOP_VIEW:
					frontViewGrid.visible = false;
					topViewGrid.visible = true;
					sideViewGrid.visible = false;
					
					break;
				case CameraViewType.LEFT_VIEW:
					frontViewGrid.visible = false;
					topViewGrid.visible = false;
					sideViewGrid.visible = true;
					
					break;
				default:
					frontViewGrid.visible = false;
					topViewGrid.visible = false;
					sideViewGrid.visible = false;
			}
		}
		private function set3DPlane(pt:Number3D=null):void
		{
			
			switch(cb.selectedLabel)
			{
				case CameraViewType.ACTUAL_VIEW:
					//if(viewToggle.label == "zx")
					//{
					convertPos.setViewPlane(new Number3D(0,0,1), pt);
					//}else
					//{
						//convertPos.setViewPlane(new Number3D(0,-1,0), pt);
					//}	
					break;
				case CameraViewType.MAIN_VIEW:
					convertPos.setViewPlane(new Number3D(0,0,1), pt);
					break;
				case CameraViewType.TOP_VIEW:
					convertPos.setViewPlane(new Number3D(0,1,0), pt);
					break;
				case CameraViewType.FRONT_VIEW:
					convertPos.setViewPlane(new Number3D(0,0,1), pt);
					break;
				case CameraViewType.LEFT_VIEW:
					convertPos.setViewPlane(new Number3D(1,0,0), pt);
					break;
			}
		}	
		private function setZoomControl(e:PropertyChangeEvent):void
		{
			//If this view is the same as recently changed camera, set this view's zoom the same.
			if(cb.selectedLabel == e.source.name)
			{
				
				if(zoomControl.value != Number(e.newValue))
				{
					zoomControl.value = Number(e.newValue);
				} 
			} 
			
		}
		private function objectAdded(e:CollectionEvent):void
		{
			var objects:ArrayCollection = wModel.objects;
			var obj:VO_Object3D = (objects.getItemAt(objects.length-1) as VO_Object3D);
				
			var vpl:ViewportLayer = viewport.getChildLayer(obj.data);
			vpl.addEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
			
			//Add right-click listener
			var menuItem:ContextMenuItem = new ContextMenuItem("Delete Object");
			menuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onDeleteObject);
			
			var customContextMenu:ContextMenu = new ContextMenu();
			customContextMenu.hideBuiltInItems();
			customContextMenu.customItems.push(menuItem);
			
			vpl.contextMenu = customContextMenu;
			
		}	
		private function setObjectVisible(name:String, visible:Boolean):void
		{
			var obj:DisplayObject3D = cObject.getObjectListByType(name)[0].data;
				
				var vpl:ViewportLayer = viewport.getChildLayer(obj);
				if(visible)
				{
					vpl.visible = true;
				}else{
					vpl.visible = false;
				}
					
		}
	}
}